using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using GoogleFeeder;
using TosObserver;
using System.Threading.Tasks.Dataflow;
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Threading;
using MaasOne.Base;
using MaasOne.Finance;
using MaasOne.Finance.YahooFinance;
using MaasOne.RSS;
using tdaactx;
using System.Reflection;

namespace Tos2Google
{
    public partial class MainForm2 : Form
    {
        public static MainForm2 StaticThis;
        MyTosFeedMonitor myTosFeedMonitor = null;
        Spreadsheets Google = null;
        Worksheet WSheet;

        public MainForm2()
        {
            InitializeComponent();
            StaticThis = this;
            myTosFeedMonitor = new MyTosFeedMonitor(this);
            if (!IsInDesigner)
            {
                StatusLabel.Text = "TD login...";
                autoAdvancer1.BehaveAsAutoLoginWants(AutoLogin.PreferredLoginMethodEnum.AutoLogin/*AutoLogin.StaticThis.PreferredLoginMethod*/);
            }
        }

        protected static bool IsInDesigner
        {
            get { return (Assembly.GetEntryAssembly() == null); } // http://bytes.com/topic/c-sharp/answers/448482-detecting-if-userconrtol-designer-run-time
        }

        private void btnLogin_Click(object sender, EventArgs e)
        {
            TD_Login.Connect(
                delegate(string sourceID, string apiVer, string username, string password)
                {
                    DisableForm(true);
                    TDAC.SourceApp = sourceID;
                    TDAC.LoginName = username;
                    TDAC.LoginPassword = password;
                    TDAC.LogLevel = AutoLogin.StaticThis.TDAC_LogLevel;
                    TDAC.STARTIT();
                });
        }

        #region echoes from the past (from WatchTower2010)
        /*
            TDAC_EventInit();    
             
        protected virtual void TDAC_EventInit()
        {
            this.TDAC.OnOrderAck += new Axtdaactx.ITDAAPICommEvents_OnOrderAckEventHandler(this.TDAC_OnOrderAck);
            this.TDAC.OnBalancesChange += new Axtdaactx.ITDAAPICommEvents_OnBalancesChangeEventHandler(this.TDAC_OnBalancesChange);
        }

        private void TDAC_OnOrderAck(object sender, Axtdaactx.ITDAAPICommEvents_OnOrderAckEvent e)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(e.ackType + ",");
            builder.Append(e.ackStr );
            if (lOrderCallBack != null)
            {
                lOrderCallBack(builder);
            }
        }
        private void TDAC_OnBalancesChange(object sender, Axtdaactx.ITDAAPICommEvents_OnBalancesChangeEvent e)
        {
            //Debug.Print("Account ID={0} AvailableFundsForTrading={1}", e.bal.AccountID, e.bal.AvailableFundsForTrading);
            //Shares.CashBalance = Convert.ToDecimal(e.bal.AvailableFundsForTrading);
            //if (this.m_CashBalanceCallback != null)
            //{
            //    m_CashBalanceCallback(Shares.CashBalance);
            //}
        }

             
             */
        #endregion

        protected void DisableForm(bool disable)
        {
            // it has strange side-effects, taking it off for now

            //Cursor = disable ? Cursors.WaitCursor : Cursors.Default;
            //Enabled = !disable;
        }

        private void TDAC_OnStatusChange(object sender, Axtdaactx.ITDAAPICommEvents_OnStatusChangeEvent e)
        {
            if (e.newStatus == TxTDAStatus.stLoggingIn)
            {
                btnLogin.Text = "trying";
                btnLogin.Enabled = false;
                return;
            }
            DisableForm(false);
            if (e.newStatus == TxTDAStatus.stLoggedIn)
                AfterSuccessfulLogin();
            if (e.newStatus == TxTDAStatus.stLoginFailed)
            {
                btnLogin.Text = "Login";
                MessageBox.Show("Login failed");
            }
            if (e.newStatus == TxTDAStatus.stLoggedOut || e.newStatus == TxTDAStatus.stLogoutFailed
                 || e.newStatus == TxTDAStatus.stKeepAliveInvalid)
            {
                timer1.Enabled = false;
                if (e.newStatus == TxTDAStatus.stKeepAliveInvalid)
                    StatusMessage("KeepAlive failed", true);
            }
            
            if (e.newStatus == TxTDAStatus.stKeepAliveValid)
                StatusMessage("KeepAlive OK", false);
        }

        protected readonly DateTime january1970 = new DateTime(1970, 1, 1); // from help: QuoteDate == days since 1/1/1970
        protected TimeSpan? TimeDiff { get; private set; }

        public bool LoggedIn = false;
        protected virtual void AfterSuccessfulLogin()
        {
            LoggedIn = true;

            splitContainer1.Panel1Collapsed = true;
            splitContainer1.Panel2.Enabled = true;

            // US Eastern Standard Time
            DateTime serverTime = january1970.AddDays(TDAC.ServerTime);
            TimeDiff = DateTime.Now - serverTime;

            timer2.Enabled = true; // ----> into Google login
        }

        public DateTime ServerNow()
        {
            if (!TimeDiff.HasValue) // it shouldn't really happen, at least when called carefully (i.e only after logged in)
                throw new ApplicationException("ServerNow() called without knowing TimeDiff");
            return DateTime.Now - TimeDiff.Value;
        }

        protected virtual void StatusMessage(string message, bool isError)
        {
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (!TDAC.LoggedIn)
            {
                StatusMessage("not LoggedIn when it should", true);
                return;
            }
            TDAC.KeepAlive();
        }

        private void autoAdvancer1_Go()
        {
            btnLogin_Click(null, null);
        }

        private void ActivexWayBase_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (myActionBlock != null)
            {
                e.Cancel = true;
                return;
            }
            if (myTosFeedMonitor != null)
                myTosFeedMonitor.Dispose();
        }

        List<string> Symbols;
        Dictionary<string, uint> SymbolIndex;
        private void timer2_Tick(object sender, EventArgs e)
        {
            timer2.Enabled = false;
            StatusLabel.Text = "Google login...";

            this.Enabled = false; // it takes so long we better express disabledness visually
            Cursor.Current = Cursors.WaitCursor;
            try
            {
                try
                {
                    Google = new Spreadsheets(AutoLogin.StaticThis.GUsername, AutoLogin.StaticThis.GPassword);
                    var sheet = Google.GetSheetByName(AutoLogin.StaticThis.SheetName);
                    WSheet = sheet.GetSheetByIndex(AutoLogin.StaticThis.WorkSheetIndex);

                    Symbols = new List<string>();
                    SymbolIndex = new Dictionary<string, uint>();
                    uint rowIndex = 2;
                    while (true)
                    {
                        var cell = WSheet[1, rowIndex];
                        if (cell == null)
                            break;
                        Symbols.Add(cell.Value);
                        SymbolIndex.Add(cell.Value, rowIndex);
                        rowIndex++;
                    }
                    if (Symbols.Count == 0)
                        throw new Exception("0 symbols, no point to continue");

                    StatusLabel.Text = "Subscribing... (" + Symbols.Count.ToString() + " symbols)";
                }
                catch (Exception ex)
                {
                    Cursor.Current = Cursors.Default;
                    MessageBox.Show("Google login or worksheet connect failed: " + ex.Message);
                    Close(); // no point to continue
                }
            }
            finally
            {
                Cursor.Current = Cursors.Default;
                this.Enabled = true;
            }

            StartOperation();
        }

        const int maxBuffered = 1000;
        private void StartOperation()
        {
            if (AutoLogin.StaticThis.ProcessMode == AutoLogin.ProcessModeEnum.DecisionEngine)
            {
                GetHistoricals();
                GetHistoricalNews();
            }

            listBoxSignals.Items.Clear();

            // avoid accessing the same cell (at the same time)
            sending = new ConcurrentDictionary<string, bool>();

            // prepare for once-is-enough ones (but we do Unsubscribe, too)
            //seen = new ConcurrentDictionary<string, bool>();

            // prepare consumer
            myActionBlock = new ActionBlock<TickData>(tickData => ProcessTickData(tickData),
                new ExecutionDataflowBlockOptions
                {
                    MaxDegreeOfParallelism = AutoLogin.StaticThis.ProcessMode == AutoLogin.ProcessModeEnum.DecisionEngine ? 1 : 8, // <------ how much Parallelism
                    BoundedCapacity = maxBuffered
                });

            // start producer
            RequestFieldsFromTos();
            StatusLabel.Text = (AutoLogin.StaticThis.ProcessMode == AutoLogin.ProcessModeEnum.DecisionEngine ? "Decision engine " : "Sync ") + "started (" + Symbols.Count.ToString() + " symbols)";
            btnStop.Enabled = true;
        }

        private void GetHistoricals()
        {
            HistQuotesDownload dl = new HistQuotesDownload();
            dl.Settings.IDs = Symbols.ToArray();
            dl.Settings.FromDate = DateTime.Today.AddDays(-40); // how many days back
            dl.Settings.ToDate = DateTime.Today;
            dl.Settings.Interval = HistQuotesInterval.Daily;

            Response<HistQuotesResult> resp = dl.Download();

            foreach (HistQuotesDataChain hqc in resp.Result.Chains)
            {
                hqc.Reverse(); // let's reverse, better to handle later
                DecisionEngine.EngineBySymbol(hqc.ID.ToUpper()).Historicals = hqc;
                /*foreach (HistQuotesData hqd in hqc)
                {
                    double close = hqd.Close;
                    double closeAdj = hqd.CloseAdjusted;
                    long volume = hqd.Volume;
                    //...
                }*/
            }
        }

        DateTime lastGetHistoricalNews = DateTime.MinValue;
        private void GetHistoricalNews()
        {
            var urls = new List<string>();
            foreach (var sym in Symbols)
                urls.Add("http://finance.yahoo.com/rss/headline?s=" + sym);
            FeedDownload dl = new FeedDownload();
            Response<FeedResult> resp = dl.Download(urls);

            foreach (Feed feed in resp.Result.Feeds)
            {
                string sym = feed.Title.Replace("Yahoo! Finance: ", string.Empty).Replace(" News", string.Empty);
                DecisionEngine.EngineBySymbol(sym.ToUpper()).News = feed;

                //string id = feed.Title;  //   Yahoo! Finance: ACAD News
                //string description = feed.Description;

                //foreach (FeedItem feedItem in feed.Items)
                //{
                //    string itemID = feedItem.Title;
                //    string itemDescription = feedItem.Description;
                //}
            }
            lastGetHistoricalNews = DateTime.Now;
            //Debug.WriteLine("news refreshed at " + DateTime.Now.ToLongTimeString());
        }

        DateTime lastGetBackfills = DateTime.MinValue;
        private void GetBackfills()
        {
            foreach (var de in DecisionEngine.EnginesPerSymbol.Values)
            {
                // make backfill history invalid (to avoid wrong decisions based on too old data)
                de.Bars10min = null;
                de.Bars1min = null;
            }
            RequestBackfills();
            lastGetBackfills = DateTime.Now;
            //Debug.WriteLine("backfills refreshed at " + DateTime.Now.ToLongTimeString());
        }

        ConcurrentDictionary<string, bool> sending;
        //ConcurrentDictionary<string, bool> seen;
        ActionBlock<TickData> myActionBlock = null;

        public void Tick(TickData tickData)
        {
            myActionBlock.Post(tickData);
        }

        private void ProcessTickData(TickData tickData)
        {
            if (myActionBlock == null)
                return;
            if (myActionBlock.InputCount >= (maxBuffered - 10))
                return; // just ignore, not nice, but we better don't make DDE listener blocked (waiting for InputCount to decrease)

            if (AutoLogin.StaticThis.ProcessMode == AutoLogin.ProcessModeEnum.DecisionEngine)
            {
                // refresh news
                TimeSpan diff = DateTime.Now - lastGetHistoricalNews;
                if (diff.TotalSeconds > 60)
                    GetHistoricalNews();

                // refresh backfills
                diff = DateTime.Now - lastGetBackfills;
                if (diff.TotalSeconds > 60)
                    GetBackfills();

                var signal = DecisionEngine.ProcessTickData(tickData);
                if (signal != null)
                    SignalMainThread(signal);

                return;
            }

            string key = tickData.Topic.Name + "@" + tickData.Symbol;

            // once-is-enough ones
            //bool firstTime = seen.TryAdd(key, true);
            //lock (SyncFields)
            //{
            //    if (!firstTime && SyncFields.ContainsKey(tickData.Topic) && SyncFields[tickData.Topic] < 0)
            //        return; 
            //}

            if (sending.TryAdd(key, true)) // ignore the tick if it's sending already, not nice, but what can we do (queue makes no sense either)
            {
                SendToGoogle(tickData);

                // done, release the slot
                bool b;
                sending.TryRemove(key, out b);
            }
            if (myActionBlock != null)
            {
                int usedCount = myActionBlock.InputCount;
                //Debug.WriteLine("......" + usedCount.ToString());
                ReportBufferFill(usedCount);
            }
        }

        private void ProcessSignal(Signal signal)
        {
            /* eventually we will have auto-buy/sell here! Something like:
            if (signal is BuySignal)
                Buy(signal.Symbol); */

            if (listBoxSignals.Items.Count > 4)
                listBoxSignals.Items.RemoveAt(4);
            listBoxSignals.Items.Insert(0, signal.ToString());
        }

        public delegate void mySignalDelegate(Signal signal);
        private void SignalMainThread(Signal signal)
        {
            if (listBoxSignals.InvokeRequired)
            {
                mySignalDelegate my = new mySignalDelegate(SignalMainThread);
                this.Invoke(my, new object[] { signal });
            }
            else
            {
                ProcessSignal(signal);
            }
        }

        public delegate void myDelegate(int usedCount);
        private void ReportBufferFill(int usedCount)
        {
            if (progressBar1.InvokeRequired)
            {
                myDelegate my = new myDelegate(ReportBufferFill);
                this.Invoke(my, new object[] { usedCount });
            }
            else
            {
                if (usedCount <= progressBar1.Maximum)
                    progressBar1.Value = usedCount;
            }
        }


        public delegate void myBackfillsDelegate();
        private void RequestBackfills()
        {
            if (btnLogin.InvokeRequired)
            {
                myBackfillsDelegate my = new myBackfillsDelegate(RequestBackfills);
                this.Invoke(my, new object[] { });
            }
            else
            {
                foreach (var sym in Symbols)
                {
                    // request both 10-minute and 1-minute bars
                    TDAC.RequestBackfill(sym, 2, 10);
                    TDAC.RequestBackfill(sym, 2, 1);
                }
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            btnStop.Enabled = false;
            StatusLabel.Text = "Stopping...";
            if (myTosFeedMonitor != null)
            {
                myTosFeedMonitor.Dispose();
                myTosFeedMonitor = null;
            }
            myActionBlock.Complete();
            //myActionBlock.Completion.Wait(); // ???
            myActionBlock = null;
            Close();
        }

        private void RequestFieldsFromTos()
        {
            foreach (var symbol in Symbols)
                foreach (var tosTopic in FullData.SyncFields.Keys)
                    myTosFeedMonitor.Subscribe(symbol, tosTopic);
        }

        private void SendToGoogle(TickData tickData)
        {
            int colIndex = FullData.SyncFields[tickData.Topic];
            if (colIndex < 0)
                myTosFeedMonitor.UnSubscribe(tickData.Symbol, tickData.Topic);
            colIndex = Math.Abs(colIndex);
            uint rowIndex = SymbolIndex[tickData.Symbol];
            CellAddress addr = new CellAddress((uint)colIndex, rowIndex);
            Cell targetCell = WSheet[addr];
            if (targetCell != null) // skip WSheet.CreateCell(addr)
                targetCell.Value = tickData.Value.ToString();
        }

        private void TDAC_OnBackfill(object sender, Axtdaactx.ITDAAPICommEvents_OnBackfillEvent e)
        {
            if (string.IsNullOrWhiteSpace(e.error))
                Tick(new TickData(e.symbol.ToUpper(), e.backfill));
        }
    }

    public class MyTosFeedMonitor : TosFeedMonitor
    {
        MainForm2 form;

        public MyTosFeedMonitor(MainForm2 form_)
        {
            form = form_;
        }

        public override void OnCompleted()
        {
        }

        public override void OnError(Exception error)
        {
            // form.AddItem("error: " + error.Message);
        }

        public override void OnNext(TosFeed value)
        {
            form.Tick(new TickData(value.Symbol, value.Topic, value.Value));
        }
    }

    public class TickData : Tuple<string, TOSTopic, decimal>
    {
        public TickData(string symbol, TOSTopic tosTopic, decimal value)
            : base(symbol, tosTopic, value)
        {
        }

        public readonly TDABackfillData Backfill = null;
        public TickData(string symbol, TDABackfillData backfill)
            : base(symbol, TOSTopic.Symbol/*just anything*/, 0.0M)
        {
            Backfill = backfill;
        }

        public string Symbol
        {
            get
            {
                return this.Item1;
            }
        }

        public TOSTopic Topic
        {
            get
            {
                return this.Item2;
            }
        }

        public decimal Value
        {
            get
            {
                return this.Item3;
            }
        }
    }
}
